package raft

import (
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"xin/raft/utils"
)

const entryIndexFileName = "log_entry_index.log" // 日志条目索引文件名
const entryFileName = "log_entry.log"            // 日志条目文件名
const entryIndexItemLen = 24                     // 日志条目索引文件中每个 item 存储的大小。EntryIndexItem
const maxMinIndexLen = 16                        // 日志条目索引文件中 min_index + max_index 占用大小。
var NilFileErr = errors.New("log is nil")        //日志组件刚刚初始化一条日志还都写入

type seekFile struct {
	file      *os.File
	maxOffset int64
}

func newSeekFile(filename string) (*seekFile, error) {
	file, err := os.OpenFile(filename, os.O_CREATE|os.O_RDWR, os.ModePerm)
	if err != nil {
		return nil, err
	}

	stat, err := file.Stat()
	if err != nil {
		return nil, err
	}

	sf := &seekFile{file: file}

	if stat.Size() > 0 {
		sf.maxOffset = stat.Size()
	}

	return sf, nil
}
func (sf *seekFile) seek(offset int64) (int64, error) {
	return sf.file.Seek(offset, io.SeekStart)
}
func (sf *seekFile) truncate(size int64) error {
	return sf.file.Truncate(size)
}

func (sf *seekFile) read(bytes []byte) error {
	_, err := sf.file.Read(bytes)
	if err != nil {
		return err
	}
	return nil
}
func (sf *seekFile) readAt(bytes []byte, offset int64) error {
	_, err := sf.file.ReadAt(bytes, offset)
	if err != nil {
		return err
	}
	return nil
}
func (sf *seekFile) readUint64At(offset int64) (uint64, error) {
	data := make([]byte, 8)
	_, err := sf.file.ReadAt(data, offset)
	if err != nil {
		return 0, err
	}

	return utils.BytesToUint64BigEndian(data), nil
}
func (sf *seekFile) readUint64() (uint64, error) {
	data := make([]byte, 8)
	_, err := sf.file.Read(data)
	if err != nil {
		return 0, err
	}

	return utils.BytesToUint64BigEndian(data), nil
}
func (sf *seekFile) readInt64At(offset int64) (int64, error) {
	data := make([]byte, 8)
	_, err := sf.file.ReadAt(data, offset)
	if err != nil {
		return 0, err
	}

	return utils.BytesToInt64BigEndian(data), nil
}
func (sf *seekFile) readInt64() (int64, error) {
	data := make([]byte, 8)
	_, err := sf.file.Read(data)
	if err != nil {
		return 0, err
	}

	return utils.BytesToInt64BigEndian(data), nil
}
func (sf *seekFile) writeUint64(v uint64) error {
	bytes := utils.Uint64ToBytesBigEndian(v)
	return sf.write(bytes)
}
func (sf *seekFile) writeUint64At(v uint64, offset int64) error {
	bytes := utils.Uint64ToBytesBigEndian(v)
	return sf.writeAt(bytes, offset)
}
func (sf *seekFile) writeInt64(v int64) error {
	bytes := utils.Int64ToBytesBigEndian(v)
	return sf.write(bytes)
}
func (sf *seekFile) writeInt64At(v int64, offset int64) error {
	bytes := utils.Int64ToBytesBigEndian(v)
	return sf.writeAt(bytes, offset)
}
func (sf *seekFile) write(bytes []byte) error {
	_, err := sf.file.Write(bytes)
	if err != nil {
		return err
	}

	sf.maxOffset += int64(len(bytes))

	return nil
}
func (sf *seekFile) writeAt(bytes []byte, offset int64) error {
	_, err := sf.file.WriteAt(bytes, offset)
	if err != nil {
		return err
	}

	sf.maxOffset += int64(len(bytes))

	return nil
}

func (sf *seekFile) sync() error {
	return sf.file.Sync()
}
func (sf *seekFile) close() error {
	return sf.file.Close()
}

// EntryIndexItem 日志索引
type EntryIndexItem struct {
	Offset int64    // 偏移量 指向日志条目文件位置
	Term   uint64   // 任期
	Kind   KindType // 日志类型
}

// EntryIndexFile 日志索引文件
type EntryIndexFile struct {
	dir      string    // 日志索引文件目录
	file     *seekFile // 日志索引文件
	maxIndex uint64    // 最大索引
	minIndex uint64    // 最小索引
}

func NewEntryIndexFile(dir string) *EntryIndexFile {
	return &EntryIndexFile{dir: dir}
}

func (eif *EntryIndexFile) Init() error {
	err := utils.MakeDirNotExist(eif.dir) // 创建目录
	if err != nil {
		return err
	}

	filename := filepath.Join(eif.dir, entryIndexFileName)

	file, err := newSeekFile(filename)
	if err != nil {
		return err
	}

	eif.file = file

	err = eif.load() // load 读取索引文件最小以及最大索引
	if err != nil {
		return err
	}

	return nil
}

func (eif *EntryIndexFile) Shutdown() error {
	err := eif.file.sync()
	if err != nil {
		return err
	}
	err = eif.file.close()
	if err != nil {
		return err
	}
	return nil
}

// load 读取已经存在的日志索引文件
func (eif *EntryIndexFile) load() error {
	if eif.file.maxOffset == 0 {
		return nil
	}

	minIndex, err := eif.file.readUint64()
	if err != nil {
		return err
	}
	maxIndex, err := eif.file.readUint64()
	if err != nil {
		return err
	}

	eif.minIndex = minIndex
	eif.maxIndex = maxIndex

	return nil
}

func (eif *EntryIndexFile) isEmpty() bool {
	return eif.maxIndex == 0 && eif.minIndex == 0 && eif.file.maxOffset == 0
}

// offset 通过计算得到索引文件偏移量
func (eif *EntryIndexFile) offset(index uint64) int64 {
	return int64((index-eif.minIndex)*entryIndexItemLen + maxMinIndexLen)
}

// getIndexFileOffset 获取日志文件偏移量
// offset int64 通过计算得到偏移量
func (eif *EntryIndexFile) getIndexFileOffset(index uint64) (int64, error) {
	if eif.isEmpty() {
		return 0, NilFileErr
	}

	offset := eif.offset(index)
	if offset >= eif.file.maxOffset {
		return 0, errors.New("offset out of range")
	}

	return eif.file.readInt64At(offset)
}

func (eif *EntryIndexFile) saveIndex(offset int64, kind KindType, term uint64, index uint64) error {
	err := eif.setMinIndex(index)
	if err != nil {
		return err
	}

	err = eif.setMaxIndex(index)
	if err != nil {
		return err
	}

	// 偏移量移动到最后
	_, err = eif.file.seek(eif.file.maxOffset)
	if err != nil {
		return fmt.Errorf("write entry index file error,seek %w", err)
	}

	err = eif.write(offset, kind, term)
	if err != nil {
		return err
	}

	return eif.file.sync()
}

func (eif *EntryIndexFile) setMinIndex(index uint64) error {
	if eif.file.maxOffset == 0 {
		eif.minIndex = index

		// 更新索引文件中的 min_index
		err := eif.file.writeUint64At(index, 0)
		if err != nil {
			return fmt.Errorf("write entry index file error,min_index %w", err)
		}
	}

	return nil
}

func (eif *EntryIndexFile) setMaxIndex(index uint64) error {
	if index != eif.maxIndex+1 {
		return fmt.Errorf("index must be %d but is %d", eif.maxIndex+1, index)
	}

	return eif.setMaxIndex0(index)
}

func (eif *EntryIndexFile) setMaxIndex0(index uint64) error {
	eif.maxIndex = index
	// 更新索引文件中的 max_index
	err := eif.file.writeUint64At(index, 8)
	if err != nil {
		return fmt.Errorf("write entry index file error,max_index %w", err)
	}
	return nil
}

func (eif *EntryIndexFile) write(offset int64, kind KindType, term uint64) error {
	err := eif.file.writeInt64(offset)
	if err != nil {
		return fmt.Errorf("write entry index file error,offset %w", err)
	}
	err = eif.file.writeUint64(uint64(kind))
	if err != nil {
		return fmt.Errorf("write entry index file error,kind %w", err)
	}
	err = eif.file.writeUint64(term)
	if err != nil {
		return fmt.Errorf("write entry index file error,term %w", err)
	}
	return nil
}

// EntryFile 日志文件
type EntryFile struct {
	dir  string    // 日志文件目录
	file *seekFile // 日志文件
}

func NewEntryFile(dir string) *EntryFile {
	return &EntryFile{dir: dir}
}

func (ef *EntryFile) Init() error {
	err := utils.MakeDirNotExist(ef.dir)
	if err != nil {
		return err
	}

	filename := filepath.Join(ef.dir, entryFileName)

	file, err := newSeekFile(filename)

	if err != nil {
		return err
	}

	ef.file = file

	return nil
}
func (ef *EntryFile) Shutdown() error {
	err := ef.file.sync()
	if err != nil {
		return err
	}
	err = ef.file.close()
	if err != nil {
		return err
	}
	return nil
}

// getEntry 获取日志条目
func (ef *EntryFile) getEntry(offset int64) (*Entry, error) {
	if offset >= ef.file.maxOffset {
		return nil, errors.New("offset out of range")
	}
	_, err := ef.file.seek(offset) // 移动偏移量
	if err != nil {
		return nil, fmt.Errorf("seek entry file error : %w", err)
	}

	kind, err := ef.file.readUint64()
	if err != nil {
		return nil, fmt.Errorf("read entry file error,kind : %w", err)
	}

	index, err := ef.file.readUint64()
	if err != nil {
		return nil, fmt.Errorf("read entry file error,index : %w", err)
	}

	term, err := ef.file.readUint64()
	if err != nil {
		return nil, fmt.Errorf("read entry file error,term : %w", err)
	}

	l, err := ef.file.readInt64()
	if err != nil {
		return nil, fmt.Errorf("read entry file error,len : %w", err)
	}

	commandBytes := make([]byte, l)
	err = ef.file.read(commandBytes)
	if err != nil {
		return nil, fmt.Errorf("read entry file error,command : %w", err)
	}

	return &Entry{
		Kind:    KindType(kind),
		Index:   index,
		Term:    term,
		Len:     l,
		Command: commandBytes,
	}, nil
}

func (ef *EntryFile) saveEntry(entry *Entry) (offset int64, err error) {
	_, err = ef.file.seek(ef.file.maxOffset)
	if err != nil {
		return 0, fmt.Errorf("seek entry file error,%w", err)
	}

	offset, err = ef.write(entry)
	if err != nil {
		return
	}

	return offset, ef.file.sync()
}

// write 写入日志条目
// 调用之前需要移动偏移量到最大偏移量位置，也就是文件的末尾
func (ef *EntryFile) write(entry *Entry) (offset int64, err error) {
	offset = ef.file.maxOffset

	err = ef.file.writeUint64(uint64(entry.Kind))
	if err != nil {
		err = fmt.Errorf("write entry kind error,index=%d term=%d kind=%d : %w", entry.Index, entry.Term, entry.Kind, err)
		return
	}

	err = ef.file.writeUint64(entry.Index)
	if err != nil {
		err = fmt.Errorf("write entry index error,index=%d term=%d kind=%d : %w", entry.Index, entry.Term, entry.Kind, err)
		return
	}

	err = ef.file.writeUint64(entry.Term)
	if err != nil {
		err = fmt.Errorf("write entry term error,index=%d term=%d kind=%d : %w", entry.Index, entry.Term, entry.Kind, err)
		return
	}

	err = ef.file.writeInt64(entry.Len)
	if err != nil {
		err = fmt.Errorf("write entry len error,index=%d term=%d kind=%d : %w", entry.Index, entry.Term, entry.Kind, err)
		return
	}

	err = ef.file.write(entry.Command)
	if err != nil {
		err = fmt.Errorf("write entry command error,index=%d term=%d kind=%d : %w", entry.Index, entry.Term, entry.Kind, err)
		return
	}

	return offset, nil
}

type LogFileStore struct {
	entryIndexFile *EntryIndexFile
	entryFile      *EntryFile
}

func NewLogFileStore(entryFileDir, entryIndexFileDir string) *LogFileStore {
	entryIndexFile := NewEntryIndexFile(entryIndexFileDir)
	entryFile := NewEntryFile(entryFileDir)

	return &LogFileStore{
		entryIndexFile: entryIndexFile,
		entryFile:      entryFile,
	}
}

// Init 初始化日志文件以及索引文件
func (f *LogFileStore) Init() error {
	err := f.entryIndexFile.Init()
	if err != nil {
		return err
	}

	err = f.entryFile.Init()
	if err != nil {
		return err
	}

	return nil
}
func (f *LogFileStore) Shutdown() error {
	err := f.entryIndexFile.Shutdown()
	if err != nil {
		return err
	}

	err = f.entryFile.Shutdown()
	if err != nil {
		return err
	}

	return nil
}

// FirstIndex 第一条日志索引
func (f *LogFileStore) FirstIndex() uint64 {
	return f.entryIndexFile.minIndex
}

// LastIndex 最后一条日志索引
func (f *LogFileStore) LastIndex() uint64 {
	return f.entryIndexFile.maxIndex
}

// GetLogEntry 获取日志条目
func (f *LogFileStore) GetLogEntry(index uint64) (*Entry, error) {
	offset, err := f.entryIndexFile.getIndexFileOffset(index)
	if err != nil {
		if errors.Is(err, NilFileErr) {
			return &Entry{
				Kind:  KindNil,
				Index: 0,
				Term:  0,
			}, nil
		}

		return nil, err
	}

	return f.entryFile.getEntry(offset)
}

// StoreLogEntry 存储日志条目
func (f *LogFileStore) StoreLogEntry(entry *Entry) error {
	// 存储日志条目
	// 存储日志条目 顺序写入 kind, index,term,len,command
	offset, err := f.entryFile.saveEntry(entry)
	if err != nil {
		return err
	}

	// 存储索引
	// 更新 max_index min_index

	err = f.entryIndexFile.saveIndex(offset, entry.Kind, entry.Term, entry.Index)
	if err != nil {
		return err
	}

	return nil
}

// StoreLogEntries 存储日志条目
// entries 必须是有序的 根据 index 从小到大
func (f *LogFileStore) StoreLogEntries(entries []*Entry) error {
	if len(entries) == 0 {
		return errors.New("entries is empty")
	}

	_, err := f.entryFile.file.seek(f.entryFile.file.maxOffset) // 日志条目文件偏移量移动到最后
	if err != nil {
		return fmt.Errorf("seek entry file error,%w", err)
	}

	err = f.entryIndexFile.setMinIndex(entries[0].Index)
	if err != nil {
		return fmt.Errorf("set min index error,%w", err)
	}
	err = f.entryIndexFile.setMaxIndex0(entries[len(entries)-1].Index)
	if err != nil {
		return fmt.Errorf("set max index error,%w", err)
	}

	_, err = f.entryIndexFile.file.seek(f.entryIndexFile.file.maxOffset) // 索引文件偏移量移动到最后
	if err != nil {
		return fmt.Errorf("seek entry index file error,%w", err)
	}

	for _, entry := range entries {
		offset, err := f.entryFile.write(entry) // 写入日志条目
		if err != nil {
			return err
		}

		err = f.entryIndexFile.write(offset, entry.Kind, entry.Term)
		if err != nil {
			return err
		}
	}

	return nil
}

// DeleteRange 删除日志条目 包含 minIndex
// 日志冲突 快照压缩 这两种情况会调用
// 假设有三个节点：A，B，C 其中A节点为领导人，日志索引已经写到了 100，其中 90 之后的索引并未提交成功。此时发生了网络分区。
// B节点当选为新的领导人，重新写入了索引90到100的日志。那么A节点恢复之后是根据任期来判断90到100的索引是否一致
// 理论上来说只能删除某一个索引之后的所有日志 不能删除中间的日志 所以 maxIndex 参数其实好像没啥作用
// 该方法并没有真实删除索引文件。仅仅是修改了，max_index max_offset。后续的继续写入日志文件会覆盖。
func (f *LogFileStore) DeleteRange(minIndex uint64) error {

	if minIndex < f.entryIndexFile.minIndex {
		return errors.New("min index is less than entryIndexFile.minIndex")
	}

	if minIndex > f.entryIndexFile.maxIndex {
		return errors.New("min index is greater than entryIndexFile.maxIndex")
	}

	offset, err := f.entryIndexFile.getIndexFileOffset(minIndex) // 获取最大索引偏移量
	if err != nil {
		if errors.Is(err, NilFileErr) {
			return nil
		}

		return err
	}

	err = f.entryIndexFile.setMaxIndex0(minIndex - 1)
	if err != nil {
		return err
	}
	f.entryFile.file.maxOffset = offset
	f.entryIndexFile.file.maxOffset = int64((minIndex-f.entryIndexFile.minIndex)*entryIndexItemLen + maxMinIndexLen)

	return nil
}

// DeleteRangeReal 真实删除日志文件内容
func (f *LogFileStore) DeleteRangeReal(minIndex uint64) error {
	if err := f.DeleteRange(minIndex); err != nil {
		return err
	}

	if err := f.entryFile.file.truncate(f.entryFile.file.maxOffset); err != nil {
		return err
	}

	if err := f.entryIndexFile.file.truncate(f.entryIndexFile.file.maxOffset); err != nil {
		return err
	}

	return nil
}
